---
title: 检索数据
description: SELECT是SQL的基础，SELECT 的作用是从一个表或多个表中检索出想要的数据行。
tags: [MySQL,DQL]
---

SELECT 的作用是从一个表或多个表中检索出想要的数据行。

## SELECT查询的基础语法
SELECT 可以帮助我们从一个表或多个表中进行数据查询。众所周知，一个数据表是由列（字段名）和行（数据行）组成的，我们要返回满足条件的数据行，就需要在 SELECT 后面加上我们想要查询的列名。

创建一个王者荣耀英雄数据表，这张表里一共有 69 个英雄，23 个属性值（不包括英雄名 name）。



表中 24 个字段（除了 ID 以外），分别代表的含义见下图。

![img](https://static001.geekbang.org/resource/image/50/44/50284b67a5c5d3f6ecc541f8dbb5ea44.png?wh=630*317)

这个表中一共有 25 个字段，除了 id 和英雄名 name 外，还存在 23 个属性值。可以使用 select * 检索出所有的列：

```sql
SELECT * FROM heros;
```

## 起别名

在检索的时候，可以给列名起别名。如下：

```sql
mysql> SELECT name as n,hp_max as hm,mp_max as mm,attack_max as am from heros;
+----------+------+------+------+
| n        | hm   | mm   | am   |
+----------+------+------+------+
| 夏侯惇   | 7350 | 1746 |  321 |
| 钟无艳   | 7000 | 1760 |  318 |
| 张飞     | 8341 |  100 |  301 |
| 牛魔     | 8476 | 1926 |  273 |
| 吕布     | 7344 |    0 |  343 |
| 亚瑟     | 8050 |    0 |  346 |
| 铠       | 6700 | 1784 |  328 |
| 百里守约 | 5611 | 1784 |  410 |
+----------+------+------+------+
69 rows in set (0.00 sec)
```

起别名的作用是对原有名称进行简化，同样也可以对表名称起别名，在多表连接查询的时候会用到。

## 去除重复行

关于单个表的 SELECT 查询，还有一个非常实用的操作，就是从结果中去掉重复的行。使用的关键字是 DISTINCT。比如查询 heros 表中关于攻击范围的取值有哪些？

```sql
mysql> SELECT DISTINCT attack_range from heros;
+--------------+
| attack_range |
+--------------+
| 近战         |
| 远程         |
+--------------+
2 rows in set (0.00 sec)
```

如果我们带上英雄名称，会是怎么样呢？

```sql
mysql> SELECT DISTINCT name, attack_range from heros;
+----------+--------------+
| name     | attack_range |
+----------+--------------+
| 夏侯惇   | 近战         |
| 钟无艳   | 近战         |
.........省略若干行.........
| 娜可露露 | 近战         |
| 兰陵王   | 近战         |
| 铠       | 近战         |
| 百里守约 | 远程         |
+----------+--------------+
69 rows in set (0.00 sec)
```

运行结果( 69条记录)：

1. DISTINCT 需要放在所有列名的前面，如果写成 `SELECT name, DISTINCT attack_range FROM heros`会报错。
2. DISTINCT 其实是对后面所有列名的组合进行去重，能看到的结果有 69 条，是因为这 69 个英雄名称不同。

## 如何排序检索数据

当我们检索数据的时候，有时候需要按照某种顺序进行结果的返回，比如我们查询所有的英雄，按照最大生命值从高到低的顺序进行排列，就需要使用 ORDER BY 子句。使用 ORDER BY 子句有以下几个点需要掌握。

1. 排序的列名：ORDER BY 后面可以有一个或多个列名，如果是多个列名进行排序，会按照后面第一个列先进行排序，当第一列的值相同的时候，再按照第二列进行排序，依次类推。
2. 排序的顺序：ORDER BY 后面可以注明排序规则，ASC 代表递增排序，DESC 代表递减排序。如果没有注明排序规则，默认情况下是按照 ASC递增排序。
3. 非选择列排序：ORDER BY 可以使用非选择列进行排序，所以即使在 SELECT 后面没有这个列名，同样可以放到 ORDER BY 后面进行排序。

如果想要显示英雄名称及最大生命值，按照最大生命值从高到低的方式进行排序：

```sql
mysql> SELECT name, hp_max FROM heros ORDER BY hp_max DESC;
+----------+--------+
| name     | hp_max |
+----------+--------+
| 廉颇     |   9328 |
| 白起     |   8638 |
| 程咬金   |   8611 |
| 刘禅     |   8581 |
| 牛魔     |   8476 |
| 张飞     |   8341 |
| 庄周     |   8149 |
| 刘邦     |   8073 |
| 武则天   |   5037 |
+----------+--------+
69 rows in set (0.00 sec)
```

如果想要显示英雄名称及最大生命值，按照最大法力从低到高，当最大法力值相等的时候按照最大生命值从高到低的方式进行排序：

```sql
mysql> SELECT name, hp_max FROM heros ORDER BY mp_max ASC,hp_max DESC;
+----------+--------+
| name     | hp_max |
+----------+--------+
| 程咬金   |   8611 |
| 亚瑟     |   8050 |
| 曹操     |   7473 |
| 吕布     |   7344 |
| 橘石京   |   7000 |
| 宫本武藏 |   6210 |
| 阿轲     |   5968 |
| 老夫子   |   7155 |
| 扁鹊     |   6703 |
| 妲己     |   5824 |
+----------+--------+
69 rows in set (0.00 sec)
```

## 约束返回结果的数量

在查询过程中，我们可以约束返回结果的数量，使用 LIMIT 关键字。比如我们想要返回英雄名称及最大生命值，按照最大生命值从高到低排序，返回 5 条记录即可。

```sql
mysql> SELECT name ,hp_max FROM heros ORDER BY hp_max DESC LIMIT 5;
+--------+--------+
| name   | hp_max |
+--------+--------+
| 廉颇   |   9328 |
| 白起   |   8638 |
| 程咬金 |   8611 |
| 刘禅   |   8581 |
| 牛魔   |   8476 |
+--------+--------+
5 rows in set (0.00 sec)
```

约束返回结果的数量可以减少数据表的网络传输量，也可以提升查询效率。这样做的好处就是 SELECT 不需要扫描完整的表，只需要检索到符合条件的记录返回即可。

## SELECT 的执行顺序

需要记住 SELECT 查询时的两个顺序：

1. 关键字的顺序：

```sql
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY
```

1. SELECT 语句的执行顺序（在MySQL 和 Oracle 中， SELECT 执行顺序基本相同）：

```sql
FROM > WHERE > GROUP BY > HAVING > SELECT的字段 > DISTINCT > ORDER BY > LIMIT
```

比如你写了一个 SQL 语句，那么它的关键字和执行顺序是下面这样的：

```sql
SELECT DISTINCT player_id, player_name, count(*) as num # 顺序5
FROM player JOIN team ON player.team_id = team.team_id # 顺序1
WHERE height > 1.80 #顺序2
GROUP BY player.team_id # 顺序 3
HAVING num > 2 #顺序 4
ORDER BY num DESC #顺序6
LIMIT 2 #顺序7
```

在 SELECT 语句执行这些步骤的时候，每个步骤都产生一个虚拟表，然后将这个虚拟表传入下一个步骤中作为输入。

首先，SELECT 是先执行 FROM 这一步的。在这个阶段，如果是多张表联查，还会经历下面的几个步骤：

1. 首先通过 CROSS JOIN 求笛卡尔积，相当于得到虚拟表 vt1-1(virtual table)；
2. 通过 ON 筛选，在虚拟表 vt1-1 的基础上进行筛选，得到虚拟表 vt1-2；
3. 添加外部行。如果我们使用的是左连接，右连接或者全连接，就会涉及到外部行，也就是在虚拟表 vt1-2的基础上增加外部行，得到虚拟表 vt1-3;

当然如果我们操作的是两张以上的表，还会重复上面的步骤，直到所有的表被处理完为止。

当我们拿到查询数据表的原始数据，也就是最终的虚拟表 vt1，就可以在此基础上进行 HWERE 阶段。在这个阶段中，会根据 vt1 表的结果进行筛选过滤，得到虚拟表 vt2。

然后进入第三步和第四步，也就是 GROUP BY 和 HAVING 阶段。在这个阶段中，实际上是在虚拟表 vt2 的基础上进行分组和分组过滤，得到中间的虚拟表 vt3 和 vt 4。

当完成筛选部分之后，就可以提取表中的字段，也就是进入到 SELECT 和 DISTINCT 阶段。

首先在 SELECT 阶段会提取想要的字段，然后再 DISTINCT 阶段过滤掉重复的行，分别得到中间的虚拟表 vt5-1 和 vt5-2。

当我们提取了想要的字段数据之后，就可以按照指定的字段进行排序，也就是 ORDER BY 阶段，得到虚拟表 vt6。

最后在写 SELECT 语句的时候，不一定存在所有的关键字，相应的阶段就会省略。



